/**
 * descriptor这个对象里包含对装饰对象的描述属性
 * configurable: true/false, 可配置与否
 * enumerable: true/false, 可枚举与否
 * writable: true/false, 可写与否
 * initializer: 静态属性的value值
 * value: 非静态属性的value值
 */

/**********************一些输出**********************/
function log(str, ...args) {
    console.log(`[decorators] [log] ${str}`, ...args);
}

function warn(str, ...args) {
    console.warn(`[decorators] [warn] ${str}`, ...args);
}

function error(str, ...args) {
    console.error(`[decorators] [error] ${str}`, ...args);
}

/**********************基础**********************/
/**
 * 设置菜单面板
 */
export const menu = function (filename: string) {
    return cc._decorator.menu(!CC_EDITOR ? '' : '我的框架/' + (filename && filename.replace(/\\|\//g, '/').split('com/').pop().split('.')[0].split('/').reduce(function (t, v, i, j) {
        return (i === 1 ? t.replace(t[0], t[0].toLocaleUpperCase()) : t) + '/' + (i < j.length - 1 ? v.replace(v[0], v[0].toLocaleUpperCase()) : v)
    })))
}

/**********************自动绑定节点**********************/
/**
 * 遍历子孙节点
 * @param {cc.Node} node 
 * @param {(node)=>Boolean} cb  返回false结束遍历
 */
function forEachChildren(node, cb) {
    if (!node || !cb) {
        return null;
    }

    const children = node.children;
    if (children.length) {
        // 广度优先
        for (let index = 0; index < children.length; index++) {
            if (cb(children[index]) === false) {
                return false
            }
        }
        // 深度其次
        for (let index = 0; index < children.length; index++) {
            if (forEachChildren(children[index], cb) === false) {
                return false;
            }
        }
    }
}

/**
 * 查找所在节点的所有子孙节点
 * @template T
 * @param {cc.Node} parent          要查询的节点
 * @param {String} name             节点名字
 * @param {{prototype: T}} type     指定节点上的某个组件
 * @param {Boolean} multiple        是否查询多个
 * @return {T}
 * 
 */
function find(parent, name, type, multiple) {
    if (!parent || !name) {
        return [];
    }

    const node = parent instanceof cc.Node ? parent : parent.node;
    if (!node.children.length) {
        return [];
    }

    let res = null;
    const result = [];
    forEachChildren(node, function (child) {
        res = (child.name === name) && ((!type || type === cc.Node) ? child : child.getComponent(type));
        if (res) {
            result.push(res);
            if (!multiple) {
                return false;
            }
        }
    })

    return result;
}

function classOf(param) {
    return toString.call(param).slice(8, -1);
}
function isArray(param) {
    return classOf(param) === 'Array';
}
function isMap(param) {
    return param !== null && typeof param === 'object' && !isArray(param);
}
function isEmpty(param) {
    return isArray(param) ? !param.length : (param === null || param === undefined);
}

// 缓存key
const autoPropertyCacheKey = '_$auto_property_attr_cache$';

// 编辑器下检查缓存的节点是否有效
const editorCheckCache = function (cache, nodeName) {
    if (!cache) {
        return false;
    }
    if (cache instanceof Array) {
        return cache.every(item => item && cc.isValid(item.node || item) && (item.node || item).name === nodeName);
    } else {
        return cache && cc.isValid(cache.node || cache) && (cache.node || cache).name === nodeName;
    }
}

function _autoProperty(...args) {
    const [prototype, key, descriptor = {}, type = cc.Node, parent, multiple = false] = args;

    const { get, set } = descriptor;
    const isGetSet = !!get && !!set;

    if (CC_DEBUG) {
        if (descriptor.value) {
            warn(`[autoProperty] {${cc.js.getClassName(prototype)}.${key}}是一个function, 这将导致它会被重新定义`);
        }
        if (!isGetSet && (get || set)) {
            warn(`[autoProperty] {${cc.js.getClassName(prototype)}.${key}}的get和set必须成对出现`);
        }
    }

    // 返回一个新的descriptor
    const newDescriptor = {
        configurable: descriptor.configurable,
        enumerable: descriptor.enumerable,
        // writable: descriptor.writable,
        get() {
            // 节点名字
            const nodeName = '$' + key;
            // 查缓存
            if (isGetSet) {
                const result = get.call(this);
                if (CC_EDITOR) {
                    if (editorCheckCache(result, nodeName)) return result;
                } else {
                    return result;
                }
            } else if (this[autoPropertyCacheKey] && !isEmpty(this[autoPropertyCacheKey][nodeName])) {
                const result = this[autoPropertyCacheKey][nodeName];
                if (CC_EDITOR) {
                    if (editorCheckCache(result, nodeName)) return result;
                } else {
                    return result;
                }
            }
            // 节点树查询
            const node = parent ? (typeof parent === 'function' ? parent.call(this, this) : this[parent]) : this.node;
            const query = find(node, nodeName, type, multiple);
            if (query.length === 0) { warn(`[autoProperty] [${this.node.name}] 未查询到名字为{${nodeName}}并带有{${type.name || type}}组件的节点`) };
            const result = multiple ? query : (query[0] || null);
            // 缓存结果
            if (query.length) {
                if (isGetSet) {
                    set.call(this, result);
                    return get.call(this);
                } else {
                    if (!this[autoPropertyCacheKey]) Object.defineProperty(this, autoPropertyCacheKey, { value: {} });
                    this[autoPropertyCacheKey][nodeName] = result;
                }
            }
            // 返回结果
            return result;
        },
        set(value) {
            if (!CC_EDITOR || value === null) {
                if (isGetSet) {
                    set.call(this, value);
                } else {
                    if (!this[autoPropertyCacheKey]) Object.defineProperty(this, autoPropertyCacheKey, { value: {} });
                    this[autoPropertyCacheKey]['$' + key] = value;
                }
            }
        }
    }

    // 在编辑面板中显示
    if (CC_EDITOR) {
        const cctype = typeof type === 'string' ? (cc.js.getClassByName(type)) : type;
        cc._decorator.property({ type: multiple ? [cctype] : cctype, tooltip: `自动在子孙节点中搜索\n名字: $${key}\n组件: ${typeof type === 'string' ? type : cc.js.getClassName(type)}` })(prototype, key, newDescriptor);
    }

    return newDescriptor;
}
/**
 * @description 
 * 节点名字只有[以$开头]才能搜索到，声明变量时可以不用$开头，
 * 默认查询节点下的cc.Node组件，可以自定义，
 * 默认返回单个结果，如果自定义为数组形式的话会返回多个符合条件的结果
 * 
 * 例:
 * @autoProperty 或者 @autoProperty(cc.Node)
 * face1: cc.Node = null;
 * this.face1 根据查询结果可能是个node也可能是null
 * 
 * 
 * @autoProperty([]) 或者 @autoProperty([cc.Node])
 * face2: cc.Node[] = [];
 * this.face2 根据查询结果可能是个node的数组也可能是[]
 * 
 * @autoProperty(cc.Sprite)
 * face3: cc.Sprite = null;
 * this.face3 根据查询结果可能是个sprite组件也可能是null
 * 
 * @autoProperty([cc.Sprite])
 * face4: cc.Sprite[] = [];
 * this.face4 根据查询结果可能是个sprite组件的数组也可能是[]
 *  
 */
export function autoProperty(options?: { type?: typeof cc.Object | [typeof cc.Object] | string | [string], parent?: string | ((self: any) => any) } | typeof cc.Object | [typeof cc.Object] | string | [string]): Function;
export function autoProperty(prototype: Object, key: string, descriptor?: any): any;
export function autoProperty(...args): any {
    // 传入自定义参数
    if (args.length <= 1) {
        let param = args[0];

        let type = null;
        let parent = null;
        let multiple = false;

        if (isMap(param)) {
            type = param.type;
            parent = param.parent;
        } else {
            type = param;
        }

        if (isArray(type)) {
            type = type[0] || cc.Node;
            multiple = true;
        }

        return function (prototype, key, descriptor) {
            return _autoProperty(prototype, key, descriptor, type, parent, multiple);
        }
    } else {
        return _autoProperty(...args)
    }
}

/**
 * 删除自动绑定的数据缓存,key为空会删除com下的全部
 */
export function delAutoPropertyCache<T extends cc.Component>(com: T, key?: string) {
    if (com && com[autoPropertyCacheKey]) {
        if (!key) {
            cc.js.clear(com[autoPropertyCacheKey]);
        } else if (com[autoPropertyCacheKey]) {
            delete com[autoPropertyCacheKey]['$' + key];
        }
    }
}

/**********************数据绑定MOBX**********************/
/**
 * 文档：https://cn.mobx.js.org/
 * CDN：https://unpkg.com/mobx/lib/mobx.umd.js
 */
/**
 * @see https://cn.mobx.js.org/
 * @see https://www.jianshu.com/p/d1e8fbc94c78
 * observable   [修饰] [观察数据变化的方法]
 * computed     [修饰] [观察计算属性的变化，可以设置参数] @see https://cn.mobx.js.org/refguide/computed-decorator.html
 * action       [修饰] [改变数值，修饰改变数值的方法]
 * action.bound [修饰] [改变数值，action中的异步方法可以调用被action.bound修饰的方法进行修改数值]
 * runInAction  [方法] [改变数值，用在action中的异步方法里或await之后来进行修改数值操作]
 * autorun      [修饰] [监听数值变化，一上来就执行，数据改变时执行，可重复执行]
 * reaction     [修饰] [监听数值变化，条件函数一上来就执行，条件函数的返回值发生变化时，效果函数将被执行，可重复执行，可手动停止]
 * when         [修饰] [监听数值变化，条件函数一上来就执行，条件函数返回值为true时，效果函数将被执行，只执行一次]
 * @example
 * // 修改数据一定要放在runInAction里面执行、也可以在被action修饰过的函数里面修改数据
 * runInAction(() => { data.a = 1; })
 * autorun(()=>{ () => { console.log(data.a); })
 * // reaction.dispose()可以手动停止监听，dispose方法也会被作为reaction的返回值返回
 * var dispose = reaction(()=>{ return data.a > 1}, (a, reaction) => { if(a > 1) reaction.dispose(); })
 * // when的条件里面写入想要监听的数据，可以是多个
 * when(()=>{ return data.a + data.b > 1}, () => { console.log(123); })
 * 
 */
import mobx from './mobx/mobx'
// const { autorun, reaction, when, configure, action, computed, observable, comparer, runInAction } = mobx;

const mobxDisposerKey = '_$mobx_ccclass_disposer$';
const mobxExecuteKey = '_$mobx_ccclass_execute$';

function mobxModifyCcclassFun(prototype, bindFunName: string, unbindFunName: string, executeName: string, funName: string, condition?: Function) {
    const bindArrKey = mobxExecuteKey + bindFunName;
    const unbindMapKey = mobxDisposerKey + unbindFunName;

    const oldBindFunName = mobxExecuteKey + bindFunName + '_old';
    const oldUnbindFunName = mobxExecuteKey + unbindFunName + '_old';

    // 待绑定数组
    if (!prototype[bindArrKey]) Object.defineProperty(prototype, bindArrKey, { value: [] })
    prototype[bindArrKey].push([executeName, funName, condition]);

    // 修改原始方法
    if (!prototype[bindFunName] || !prototype[bindFunName].mobx_ccclass_bind) {
        // 缓存原方法
        if (prototype[bindFunName]) {
            Object.defineProperty(prototype, oldBindFunName, { value: prototype[bindFunName] });
        } else if (CC_DEBUG) {
            log(`[${executeName}] ${cc.js.getClassName(prototype)}.${bindFunName}方法已自动添加`);
        }
        // 定义新方法
        prototype[bindFunName] = function (...args) {
            this[oldBindFunName] && this[oldBindFunName](...args);

            // 待解绑Map
            if (!this[unbindMapKey]) Object.defineProperty(this, unbindMapKey, { value: Object.create(null) });
            this[bindArrKey].forEach((arg) => {
                // 已经存在先停止旧的
                if (this[unbindMapKey][arg[1]]) {
                    this[unbindMapKey][arg[1]]();
                }
                // 再生成新的
                if (arg[2]) {
                    this[unbindMapKey][arg[1]] = mobx[arg[0]](arg[2].bind(this, this), this[arg[1]].bind(this));
                } else {
                    this[unbindMapKey][arg[1]] = mobx[arg[0]](this[arg[1]].bind(this));
                }
            })
        }
        prototype[bindFunName].mobx_ccclass_bind = true;
    }

    if (!prototype[unbindFunName] || !prototype[unbindFunName].mobx_ccclass_unbind) {
        if (prototype[unbindFunName]) {
            Object.defineProperty(prototype, oldUnbindFunName, { value: prototype[unbindFunName] });
        } else if (CC_DEBUG) {
            log(`[${executeName}] ${cc.js.getClassName(prototype)}.${unbindFunName}方法已自动添加`);
        }
        prototype[unbindFunName] = function (...args) {
            for (const key in this[unbindMapKey]) {
                this[unbindMapKey][key]();
            }
            cc.js.clear(this[unbindMapKey]);
            this[oldUnbindFunName] && this[oldUnbindFunName](...args);
        }
        prototype[unbindFunName].mobx_ccclass_unbind = true;
    }
}


function _ccautorun(...args) {
    const [prototype, funName, descriptor, bindFunName = 'onLoad', unbindFunName = 'onDestroy'] = args;

    // 某些key不允许被修饰
    if (funName === 'constructor') {
        return error('[autorun] 禁止修饰constructor');
    }

    // 修饰的必须是个方法
    if (typeof descriptor.value !== 'function') {
        return error('[autorun] 只允许修饰方法, 禁止修饰变量或get/set');
    }

    mobxModifyCcclassFun(prototype, bindFunName, unbindFunName, 'autorun', funName);
}
/**
 * 对mobx中autorun的封装
 * 以确保在autorun将在指定的函数被调用时才执行或销毁(执行顺序早于这个函数原本的内容)
 * 这个函数一般是cc的声明周期函数，默认是onLoad绑定，onDestory解绑，可以自定义
 * @example
 * @ccautorun('start', 'onDisable')
 * test(){ console.log(data.num) }
 * autorun将在start被调用时执行，在onDisable调用时销毁
 */
export function ccautorun(bind?: string, unbind?: string): Function;
export function ccautorun(prototype: Object, key: any, descriptor?: any): any;
export function ccautorun(...args): any {
    if (typeof args[2] === 'undefined' || typeof args[2].configurable === 'undefined') {
        // 传入自定义参数
        return function (prototype, key, descriptor) {
            _ccautorun(prototype, key, descriptor, args[0], args[1]);
        }
    } else {
        _ccautorun(...args);
    }
}


function _ccreaction(...args) {
    const [prototype, funName, descriptor, condition, bindFunName = 'onLoad', unbindFunName = 'onDestroy'] = args;

    // 某些key不允许被修饰
    if (funName === 'constructor') {
        return error('[autorun] 禁止修饰constructor');
    }

    // 修饰的必须是个方法
    if (typeof descriptor.value !== 'function') {
        return error('[autorun] 只允许修饰方法, 禁止修饰变量或get/set');
    }

    mobxModifyCcclassFun(prototype, bindFunName, unbindFunName, 'reaction', funName, condition);
}
/**
 * 对mobx中reaction的封装
 * 以确保在reaction将在指定的函数被调用时才执行或销毁(执行顺序早于这个函数原本的内容)
 * 这个函数一般是cc的声明周期函数，默认是onLoad绑定，onDestory解绑，可以自定义
 * @example
 * @ccreaction(function(){ retrun data.num > 10 }, 'start', 'onDisable')
 * test(){ console.log(data.num) }
 * reaction将在start被调用时执行，在onDisable调用时销毁
 */
export function ccreaction(condition: Function, bind?: string, unbind?: string): Function;
// export function ccreaction(prototype: Object, key: any, descriptor?: any): any;
export function ccreaction(...args) {
    // 传入自定义参数
    if (typeof args[2] === 'undefined' || typeof args[2].configurable === 'undefined') {
        return function (prototype, key, descriptor) {
            _ccreaction(prototype, key, descriptor, args[0], args[1], args[2]);
        }
    } else {
        _ccreaction(...args)
    }
}


function _ccwhen(...args) {
    const [prototype, funName, descriptor, condition, bindFunName = 'onLoad', unbindFunName = 'onDestroy'] = args;

    // 某些key不允许被修饰
    if (funName === 'constructor') {
        return error('[autorun] 禁止修饰constructor');
    }

    // 修饰的必须是个方法
    if (typeof descriptor.value !== 'function') {
        return error('[autorun] 只允许修饰方法, 禁止修饰变量或get/set');
    }

    mobxModifyCcclassFun(prototype, bindFunName, unbindFunName, 'when', funName, condition);
}
/**
 * 对mobx中when的封装
 * 以确保在when将在指定的函数被调用时才执行或销毁(执行顺序早于这个函数原本的内容)
 * 这个函数一般是cc的声明周期函数，默认是onLoad绑定，onDestory解绑，可以自定义
 * @example
 * @ccwhen(function(){ retrun data.num > 10 }, 'start', 'onDisable')
 * test(){ console.log(data.num) }
 * when将在start被调用时执行，在onDisable调用时销毁
 */
export function ccwhen(condition: Function, bind?: string, unbind?: string): Function;
// export function ccwhen(prototype: Object, key: any, descriptor?: any): any;
export function ccwhen(...args) {
    // 传入自定义参数
    if (typeof args[2] === 'undefined' || typeof args[2].configurable === 'undefined') {
        return function (prototype, key, descriptor) {
            _ccwhen(prototype, key, descriptor, args[0], args[1], args[2]);
        }
    } else {
        _ccwhen(...args);
    }
}

export function ccaction(...args) {
    return mobx.action(...args);
}

export function ccobservable(...args) {
    return mobx.observable(...args);
}

export function cccomputed(...args) {
    return mobx.computed(...args);
}